import test.lesson.Maybe

/**
 * author:vesper.z
 * date: 2024  10:23
 * class: Test12
 * 对象表达式和对象声明
 * Kotlin 用对象表达式和对象声明来实现创建一个对某个类做了轻微改动的类的对象，且并不需要去声明一个新的子类。
 *
 **/
class Test12 {

}

class Client {
    lateinit var r: Runable
    fun addListener(r: Runable) {
        this.r = r
    }

    fun callStart() {
        this.r.onStart()
    }

    fun callEnd() {
        this.r.onEnd()
    }
}

/**
 * 对象表达式
 * 通过对象表达式实现一个匿名内部类的对象用于方法的参数中：
 */
interface Runable {
    fun onStart()
    fun onEnd()
}

//对象可以继承某个基类，或者实现其他的接口
open class XXX(x: Int) {
    open val x: Int = x
}

interface YYY

var mObj: XXX = object : XXX(1), YYY {
    override val x: Int
        get() = 15
}
//如果超类有一个构造函数，则必须传递参数给它。多个超类型和接口可以用逗号分隔。
//通过对象表达式可以越过类的定义直接得到一个对象。

fun testObj() {
    val obj1 = object {
        var name: String = "匿名内部类"
        var age: Int = 25
    }
    println(obj1.name)
}

//匿名对象可以作用只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型，
open class MM {
    //    私有函数，所以其返回类型是匿名对象
    private fun testA(a: String) = object {
        val name = a
    }

    //    公有函数，所有其返回类型是Any
    fun testB(b: String) = object {
        var name = b
    }

    fun test() {
        var a = testA("123").name
//        var b = testB("123").name //错误： Unresolved reference: name
    }
}

/**
 * 对象声明
 * Kotlin 使用object关键字来声明一个对象。
 * Kotlin 中可以方便的通过对象声明来获取一个单例
 */
object MManager {
    var value:String = "123"
    fun testA(): Int {
        return 123
    }
    fun testB(): String {
        return "321"
    }
}

//引用该对象，直接使用其名称即可
var m1 = MManager.testA()
var m2 = MManager.testB()
//定义一个变量来获取这个对象，当定义两个不同的变量来获取这个对象时，不能得到两个不同的变量。也就是说通过这种方式，我们获得一个单例。
var o1 = MManager
var o2 = MManager

//对象可以有超类
object ManagerA:MM()

//与对象表达式不同，当归对象声明在另一个类的内部时，这个对象并不能通过外部类的实例访问到该对象，而只能通过类名来访问，
// 同样该对象也不能直接访问到外部类的方法和变量。

class Site{
    var param ="aaa"
    object MObj{
        val name = "bbb"
        fun testA(){
            println("123")
        }
    }
}
fun testObj2(){
    var site = Site()
    site.param
    //println(site.Mobj.name)//不能通过外部类的实例来访问匿名对象
    //通过类名来访问内部匿名对象
    Site.MObj.testA()
    println(Site.MObj.name)
}

/**
 * 伴生对象
 * 类的内部对象的声明可以使用companion关键字标记，这样就可以跟外部类关联在一起，我们就可以通过外部类访问到对象的内部元素。
 */
class MClass{
    companion object{
        val title = "伴生对象的标题"
        fun call(){
            println(title)
        }
    }
}
fun testCompanion(){
//    下面的两种写法作用相同
    println(MClass.Companion.title)
    println(MClass.title)
    //这里的Companion可以省略 相当于可以直接通过类名调用伴生对象的属性和方法
    println(MClass.call())
}

fun main(args: Array<String>) {
    println("""
        对象表达式和对象声明之前的区别
        1、对象表达式式在调用他们的地方被执行
        2、对象声明是在第一次被调用的时候完成初始化
        3、伴生对象的初始化是相应的类被加载（解析）时，与java静态初始化器的语义相匹配
        
    """.trimIndent())

    println("测试伴生对象的调用")
    testCompanion()

    println("测试访问类内部的匿名对象")
    testObj2()

    println("测试对象的超类")
    ManagerA.testB("123")


    println("测试单例")
    o1.value = "456"
    println(o2.value)

    println("测试匿名对象")
    println(m1)
    println(m2)

    println("测试匿名函数")
    var mm = MM()
    mm.test()

    println("测试声明的对象")
    testObj()

    println("测试对象集成类和实现接口")
    println(mObj.x)

    println("测试接口回调")
    var client = Client()
    client.addListener(object : Runable {
        override fun onStart() {
            println("onStart")
        }

        override fun onEnd() {
            println("onEnd")
        }
    })
    client.callStart()
    client.callEnd()
}